#include <SDL2/SDL.h>
#include <stdio.h>

// https://wiki.libsdl.org/SDL_GameControllerAddMapping?highlight=%28%5CbCategoryGameController%5Cb%29%7C%28CategoryEnum%29
// https://www.libsdl.org/release/SDL-1.2.15/docs/html/guideinput.html
// https://www.libsdl.org/release/SDL-1.2.15/docs/html/joystick.html
// https://www.libsdl.org/release/SDL-1.2.15/docs/html/eventstructures.html

// joystick event
/*
Name

SDL_JoyAxisEvent -- Joystick axis motion event structure
Structure Definition

typedef struct{
  Uint8 type;
  Uint8 which;
  Uint8 axis;
  Sint16 value;
} SDL_JoyAxisEvent;
Structure Data

type	SDL_JOYAXISMOTION
which	Joystick device index
axis	Joystick axis index
value	Axis value (range: -32768 to 32767)

Structure Definition

typedef struct{
  Uint8 type;
  Uint8 which;
  Uint8 button;
  Uint8 state;
} SDL_JoyButtonEvent;
Structure Data

type	SDL_JOYBUTTONDOWN or SDL_JOYBUTTONUP
which	Joystick device index
button	Joystick button index
state	SDL_PRESSED or SDL_RELEASED

Structure Definition

typedef struct{
  Uint8 type;
  Uint8 which;
  Uint8 hat;
  Uint8 value;
} SDL_JoyHatEvent;
Structure Data

type	SDL_JOY
which	Joystick device index
hat	Joystick hat index
value	Hat position


Structure Definition

typedef struct{
  Uint8 type;
  Uint8 which;SDL_JoystickName
  Uint8 ball;
  Sint16 xrel, yrel;
} SDL_JoyBallEvent;
Structure Data

type	SDL_JOYBALLMOTION
which	Joystick device index
ball	Joystick trackball index
xrel, yrel	The relative motion in the X/Y direction

 */

#define buf_len 1024
#define max_joystick_keys 100

typedef unsigned int uint32_t;
int char_buf[buf_len];

// joystick button: 4, joystick aixs: 1, joystick hat: 2, joystick ball: 3 , other key: 0
#define KEY_TYPE_INVAILD 0x80
#define KEY_TYPE_OTHER_KEY 0
#define KEY_TYPE_JOYSTICK_AIXS 1
#define KEY_TYPE_JOYSTICK_HAT 2
#define KEY_TYPE_JOYSTICK_BALL 3
#define KEY_TYPE_JOYSTICK_BUTTTON 4

// #define KEY_MASK_TYPE 0x90000000
// #define KEY_MASK_HAT_INX 0x0FF00000
// #define KEY_MASK_NUMBER 0x0000FFFF

// #define HAT_INX_VAL(v) (KEY_MASK_HAT_INX && (v << 20))
// #define NUMBER_VAL(v) (v && KEY_MASK_NUMBER)

char *key_names[] = {"a", "b", "y", "x", "start", "guide", "back",
                     "dpup", "dpleft", "dpdown", "dpright",
                     "leftshoulder", "rightshoulder", "leftstick", "rightstick",
                     "leftx", "lefty", "rightx", "righty",
                     "lefttrigger", "righttrigger"
                    };

#define key_names_len (sizeof(key_names)/sizeof(char *))

typedef struct key_map {
	uint8_t type;        //
	uint8_t devices_inx; //
	uint8_t position;    // hat key position
	uint8_t inx;         // key inx
} KeyData;

KeyData key_codes[max_joystick_keys];
int sel_joysticks_inx = -1;

static void safe_flush(FILE *fd)
{
	while (fgetc(fd) != EOF && fgetc(fd) != '\n');
}

static int input_int(const char * msg)
{
	int val;
	int status;
	do {
		printf("%s int char:", msg);
		status = scanf("%d", &val);
		// printf("input status: %d, val: %d\n", status, val);
		if (status == EOF || status != 1) {
			safe_flush(stdin);
		} else
			break;
	} while (1);
	return val;
}

static void info_joystick(int joystick_inx)
{
	SDL_Joystick *joystick = SDL_JoystickOpen(joystick_inx);
	if (!joystick) {
		printf("open joystick %d error\n", joystick_inx);
		return;
	}
	printf("Name: %s\n", SDL_JoystickName(joystick));
	printf("Number of Axes: %d\n", SDL_JoystickNumAxes(joystick));
	printf("Number of Buttons: %d\n", SDL_JoystickNumButtons(joystick));
	printf("Number of Balls: %d\n", SDL_JoystickNumBalls(joystick));
	printf("Number of Hats: %d\n", SDL_JoystickNumHats(joystick));
	SDL_JoystickClose(joystick);
}

static void select_joystick()
{
	SDL_Joystick *joystick = NULL;

	for (int i = 0; i < SDL_NumJoysticks(); i++) {
		joystick = SDL_JoystickOpen(i);
		if (!joystick)
			printf("open joystick failed: inx: %d\n", i);
		printf("Joystick devices %s, inx: %d\n", SDL_JoystickName(joystick), i);
		SDL_JoystickClose(joystick);
	}
	if (SDL_NumJoysticks() == 1) {
		sel_joysticks_inx = 0;
	} else {
		sel_joysticks_inx = input_int("please select joystick inx");
	}
	printf("select joystick inx : %d\n", sel_joysticks_inx);
}

static int flush_evnets()
{
	SDL_Joystick *joystick;
	SDL_JoystickEventState(SDL_ENABLE);
	joystick = SDL_JoystickOpen(sel_joysticks_inx);
	if (!joystick) {
		// printf("input joystick open joystick failed\n");
		return 0;
	}
	SDL_Event event;
	// printf("please release all key press\n");
	for (int i = 0; i < 200; i++) {
		while (SDL_PollEvent(&event)) {
			switch (event.type) {
			case SDL_QUIT:
				exit(1);
			}
		}
		usleep(1000);
	}
	SDL_JoystickClose(joystick);
}

// #define input_joy_printf(x...) printf(x)
#define input_joy_printf(x...)
int quit_count = 0;
static int input_joystick_key(const char *msg, KeyData *pkeydata)
{
	SDL_Joystick *joystick;
	SDL_JoystickEventState(SDL_ENABLE);
	joystick = SDL_JoystickOpen(sel_joysticks_inx);
	if (!joystick) {
		printf("input joystick open joystick failed\n"); SDL_Event event;
		return -1;
	}

	int selected = 0;
	SDL_Event event;
	printf("%s: ", msg);
	fflush(stdout);
	while (!selected) {
		while (SDL_PollEvent(&event)) {
			selected = 1;
			if (event.type != SDL_QUIT) {
				quit_count = 0;
			}
			switch (event.type) {
			case SDL_QUIT:
				quit_count ++;
				return -1;
				break;
			case SDL_JOYAXISMOTION:  // Handle Joystick Motion
				pkeydata->type = KEY_TYPE_JOYSTICK_AIXS;
				pkeydata->inx = event.jaxis.axis;
				input_joy_printf("type: %d which: %d axis: %d value: %d\n",
				                 event.jaxis.type, event.jaxis.which, event.jaxis.axis, event.jaxis.value);
				break;
			case SDL_JOYHATMOTION:  /* Handle Hat Motion */
				input_joy_printf("event name SDL_JOYHATMOTION: type: %d, hat: %d, value: %d \n",
				                 event.type, event.jhat.hat, event.jhat.value);
				pkeydata->type = KEY_TYPE_JOYSTICK_HAT;
				pkeydata->position = event.jhat.value;
				pkeydata->inx = event.jhat.hat;
				break;
			case SDL_JOYBUTTONDOWN:  /* Handle Joystick Button Presses */
				input_joy_printf("event name: SDL_JOYBUTTONDOWN: type: %d, button: %d\n",
				                 event.type, event.jbutton.button);
				pkeydata->type = KEY_TYPE_JOYSTICK_BUTTTON;
				pkeydata->inx = event.jbutton.button;
				break;
			case SDL_JOYBALLMOTION:  /* Handle Joyball Motion */
				input_joy_printf("event name: SDL_JOYBALLMOTION: type: %d, ball: %d\n",
				                 event.type, event.jball.ball);
				pkeydata->type = KEY_TYPE_JOYSTICK_BALL;
				pkeydata->inx = event.jball.ball;
				break;
			default:
				input_joy_printf("default event: %d\n", event.type);
				selected = 0;
				printf("\n%s: ", msg);
				fflush(stdout);
			}
		}
		usleep(1000);
	}
	SDL_JoystickClose(joystick);
	return 0;
}

static void str_key_inx(KeyData *pkeydata, char *buf)
{
	switch (pkeydata->type) {
	case KEY_TYPE_JOYSTICK_BUTTTON:
		sprintf(buf, "b%d", pkeydata->inx);
		break;
	case KEY_TYPE_JOYSTICK_AIXS:
		sprintf(buf, "a%d", pkeydata->inx);
		break;
	case KEY_TYPE_JOYSTICK_BALL:
		sprintf(buf, "ball%d", pkeydata->inx);
		break;
	case KEY_TYPE_JOYSTICK_HAT:
		sprintf(buf, "h%d.%d", pkeydata->inx, pkeydata->position);
		break;
	case KEY_TYPE_INVAILD:
		sprintf(buf, "b%d", 30);
		break;
	default:
		sprintf(buf, "b%d", 31);
	}
}

static void print_keydata(KeyData *pkeydata)
{
	switch (pkeydata->type) {
	case KEY_TYPE_JOYSTICK_BUTTTON:
		printf("button : %d ", pkeydata->inx);
		break;
	case KEY_TYPE_JOYSTICK_AIXS:
		printf("aixs  : %d ", pkeydata->inx);
		break;
	case KEY_TYPE_JOYSTICK_BALL:
		printf("ball  : %d ", pkeydata->inx);
		break;
	case KEY_TYPE_JOYSTICK_HAT:
		printf("hat   : %d.%d ", pkeydata->inx, pkeydata->position);
		break;
	default:
		printf("default: type: 0x%x", pkeydata->type);
	}
}

/**
 *  检查成功返回-1, 否则返回已设置的索引，忽略无效key
 * @param  input_keydata [description]
 * @param  cur_input_inx [description]
 * @return               [description]
 */
static int check_input_key(KeyData *input_keydata, int cur_input_inx)
{
	if (input_keydata->type == KEY_TYPE_INVAILD) return -1;
	for (int i = 0; i < cur_input_inx; i++) {
		if (key_codes[i].type == input_keydata->type &&
		        key_codes[i].inx == input_keydata->inx &&
		        key_codes[i].position == input_keydata->position) {
			return i;
		}
	}
	return -1;
}

static void input_gamecontroller_map_key()
{
	int keys_count = key_names_len;
	char str_buf[1024];
	KeyData keydata ;
	int result;
	printf("keys_count len: %d\n", keys_count);

	printf("type 1 time [Ctrl-C] to set empty map\n");
	printf("type 2 time [Ctrl-C] to reset input\n");
	printf("type 3 time [Ctrl-C] to exit program\n");
	for (int i = 0; i < keys_count; i++) {
		if (i == 0) {
			printf("\n\nstart new map\n");
		}
		sprintf(str_buf, "please set inx: %2d [%15s] keydata: ", i, key_names[i]);
		memset(&keydata, 0, sizeof(keydata));
		flush_evnets();
		input_joystick_key(str_buf, &keydata);
		// reset input map
		if (quit_count == 1) {
			// empty
			keydata.type = KEY_TYPE_INVAILD;
		} else if (quit_count == 2) {
			i = -1;
			continue;
		} else if (quit_count > 2) {
			exit(1);
		}
		result = check_input_key(&keydata, i);
		if (result >= 0) {
			printf("input keydata error this key had be mapped to [%s], please reinput or reset map\n",
			       key_names[result]);
			i--;
			continue;
		}
		memcpy(key_codes + i, &keydata, sizeof(keydata));
		print_keydata(&keydata);
		printf("\n");
	}
}

static void fomrat_sdl_gamecontrller_map_str(int joystick_inx)
{
	char buf_key[1024];
	char out_buf[1024];
	int pos = 0;
	// char *ms = "<uuid>,Afterglow PS3 Controller,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b8,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftshoulder:b4,rightshoulder:b5,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7";

	SDL_Joystick *joystick = SDL_JoystickOpen(joystick_inx);
	if (!joystick) {
		printf("open joystick %d error\n", joystick_inx);
		return;
	}
	SDL_JoystickGUID guid = SDL_JoystickGetDeviceGUID(joystick_inx);
	SDL_JoystickGetGUIDString(guid, buf_key, 256);
	pos += sprintf(out_buf, "%s,", buf_key);
	pos += sprintf(out_buf + pos, "%s,", SDL_JoystickName(joystick));
	SDL_JoystickClose(joystick);

	for (int i = 0; i < key_names_len; i++) {
		str_key_inx(&key_codes[i], buf_key);
		pos += sprintf(out_buf + pos, "%s:%s,", key_names[i], buf_key);
	}
	pos += sprintf(out_buf + pos, "platform:Linux,");
	out_buf[pos] = 0;
	int status = SDL_GameControllerAddMapping(out_buf);
	if (status == -1) {
		printf("map data is error: %s\n", SDL_GetError());
	} else {
		printf("map data is correct\n");
		printf("========map data===========\n");
		printf("%s\n", out_buf);
		printf("===========================\n");
		printf("if ppsspp game emulator please copy map data to [gamecontrollerdb.txt] file\n");
		printf("and the gamecontrollerdb.txt in ppsspp cmd boot dir\n");
	}
}


static void verify_gamecontrollerdb_file(int joystick_inx)
{

	const char *dbPath = "gamecontrollerdb.txt";
	printf("loading control pad mappings from %s\n", dbPath);

	if (SDL_IsGameController(joystick_inx)) {
		printf("mapped before is game controller\n");
	} else {
		printf("mapped before is not game controller\n");
	}

	// size_t size;
	// char *mappingData = VFSReadFile(dbPath, &size);
	// if (mappingData) {
	// 	SDL_RWops *rw = SDL_RWFromConstMem(mappingData, size);
	// 	// 1 to free the rw after use
	// 	if (SDL_GameControllerAddMappingsFromRW(rw, 1) == -1) {
	// 		printf("Failed to read mapping data - corrupt?\n");
	// 	}
	// } else {
	// 	printf("gamecontrollerdb.txt missing\n");
	// }

	if (SDL_GameControllerAddMappingsFromFile(dbPath) == -1) {
		printf("map error: %s\n", SDL_GetError());
		return;
	}
	// const char *map_str = "03000000bc2000006412000011010000,BETOP CONTROLLER,a:b2,b:b1,y:b0,x:b3,start:b9,guide:b30,back:b8,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftshoulder:b4,rightshoulder:b5,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7";
	// const char *map_str = "03000000bc2000006412000011010000,BETOP CONTROLLER,a:b2,b:b1,y:b0,x:b3,start:b9,guide:b30,back:b8,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftshoulder:b4,rightshoulder:b5,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,platform:Linux,";
	// if (SDL_GameControllerAddMapping(map_str) == -1) {
	// 	printf("map str error: %d\n", SDL_GetError());
	// 	return;
	// }

	SDL_JoystickGUID guid = SDL_JoystickGetDeviceGUID(joystick_inx);
	printf("map data: [%s], error: %s\n", SDL_GameControllerMappingForGUID(guid), SDL_GetError());

	if (SDL_IsGameController(joystick_inx)) {
		printf("mapped after is game controller\n");
	} else {
		printf("mapped after alwyas not game controller\n");
	}
}

// usage: $0 [v]
int main(int argc, char const *argv[])
{
	if (SDL_Init(SDL_INIT_JOYSTICK) < 0)
	{
		fprintf(stderr, "Couldn't initialize SDL: %s\n", SDL_GetError());
		exit(1);
	}

	select_joystick();

	if (argc > 1) {
		verify_gamecontrollerdb_file(sel_joysticks_inx);
	} else {
		info_joystick(sel_joysticks_inx);
		input_gamecontroller_map_key();
		fomrat_sdl_gamecontrller_map_str(sel_joysticks_inx);
	}

	SDL_Quit();
	return 0;
}